/*
 *  BasicTransactionsLog.java        22-ott-2009
 *   
 * Copyright (c) 2009 mentalsmash.org
 *  
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *  http://www.apache.org/licenses/LICENSE-2.0
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 * 
 *  The use of the Apache License does not indicate that this project is
 *  affiliated with the Apache Software Foundation.
 */

package org.mentalsmash.tazio.transactions.control.impl;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;
import java.util.Map.Entry;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.mentalsmash.tazio.IllegalArgumentException;
import org.mentalsmash.tazio.TazioRuntimeException;
import org.mentalsmash.tazio.transactions.control.TazioTransaction;
import org.mentalsmash.tazio.transactions.control.TransactionsLog;
import org.mentalsmash.tazio.transactions.control.TxStateObserver;
import org.mentalsmash.tazio.transactions.model.IllegalSeqNoException;
import org.mentalsmash.tazio.transactions.model.identifiers.TID;
import org.mentalsmash.tazio.transactions.user.Transaction;
import org.mentalsmash.tazio.transactions.user.TransactionStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * An instance of <tt>BasicTransactionLogManager</tt> keeps track of
 * transactions in the local system.
 * <p>
 * Whenever a transactions enters in the systems (either because it is locally
 * started or because it is received for validation) the log has to keep track
 * of that transaction and record it, until its retention has no longer meaning
 * for the system.
 * </p>
 * 
 * @author Andrea Reale
 * @version 0.0.1
 */
@Singleton
public class BasicTransactionsLog implements TxStateObserver, TransactionsLog {
    private final static Logger log = LoggerFactory
	    .getLogger(BasicTransactionsLog.class);

    /**
     * The number of minutes a Transaction is kept in the log after it is marked
     * as "cleanable".
     * <p>
     * A transaction is marked as "cleanable" if no other active transaction may
     * need it for its validation process.
     * </p>
     */
    public final static int TX_EXPIRATION_TIME_DEFAULT = 20;

    public final static String LOG_NAME = "TransactionsLog";

    /*
     * TreeMap are maps the are kept in key order, and provide fast submaps
     * access at the expense of noncostant random access time (it is log(n) )
     * Moreover it is thread safe
     */
    private final TreeMap<Long, LogBucket> activeTxs;
    /*
     * Keeps the current size of all the transactions in the activeTxs structure
     */
    private int activeTxSize = 0;

    private final TreeMap<Long, TazioTransaction> committedTxs;

    /**
     * I don't need to keep track of the full details of aborted transactions
     * data since they are not used for system's correct behaviour. This list is
     * used only to provide fast access to the entries of aborted transaction in
     * the transaciton's hash table.
     * <p>
     * It is ordered by ascending <code>seqNo</code>
     * </p>
     */
    private final LinkedList<TID> abortedTxs;

    /*
     * Structure used to provide fast lookup by TID
     */
    private final HashMap<TID, HashTableEntry> txTable;

    private volatile int unGiorno = TX_EXPIRATION_TIME_DEFAULT; // in minutes

    // used to control accesses to the lock
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * Creates a new empty <tt>BasicTransactionsLog</tt>
     */
    @Inject
    BasicTransactionsLog() {
	this.activeTxs = Maps.newTreeMap();
	this.committedTxs = Maps.newTreeMap();
	this.abortedTxs = Lists.newLinkedList();
	this.txTable = Maps.newHashMap();
    }

    /**
     * The number of minutes a Transaction is kept in the log after it is marked
     * as "cleanable".
     * <p>
     * A transaction is marked as "cleanable" if no other active transaction may
     * need it for its validation process.
     * </p>
     * 
     * @return the txExpiryTime
     */
    public int getTransactionValidity() {
	return this.unGiorno;
    }

    /**
     * 
     * The number of minutes a Transaction is kept in the log after it is marked
     * as "cleanable".
     * <p>
     * A transaction is marked as "cleanable" if no other active transaction may
     * need it for its validation process.
     * </p>
     * 
     * @param txExpiryTime
     *            the txExpiryTime to set
     */
    public void setTransactionValidity(final int txExpiryTime) {
	if (txExpiryTime < 0) {
	    log
		    .warn("Cannot set tx expiration time to a negative value, setting to default");
	    this.unGiorno = TX_EXPIRATION_TIME_DEFAULT;
	} else {
	    this.unGiorno = txExpiryTime;
	}
    }

    /**
     * Cleans up entries in the <tt>TransactionsLog</tt> removing old
     * transactions that are no longer used for the correct behaviour of the
     * system: i.e. it removes from the log every NON-ACITVE transaction having
     * a sequence number that is smaller then the sequence number of every
     * ACTIVE transaction in the log.
     * <p>
     * Since on RM side may arrive requests enquiring about the outcome of old
     * transactions this method does not completely erase transaction's data
     * from the log, but it just sets to <code> null</code> data structures that
     * are no more needed.
     * </p>
     * <p>
     * Only if a transaction has passed its expiration time it is completely
     * removed from the log
     * </p>
     * 
     * @see #purgeLog()
     */
    protected void cleanLogs() {
	cleanAbortedLog();
	cleanCommittedLog();
    }

    /**
     * Cleans the log of committed transactions.
     * <p>
     * I can remove the details for every committed transactions having
     * sequencenumber that is strictly smaller then the greates sequence number
     * of currently active transactions
     * </p>
     */
    protected void cleanCommittedLog() {
	log.debug("Cleaning committed transactions from the log..");
	this.lock.writeLock().lock();
	if (this.committedTxs.isEmpty()) {
	    log.debug("Nothing to clean: no committed transaction in the log");
	    return;
	}
	try {
	    long maxActiveSeqNo;
	    if (!this.activeTxs.isEmpty()) {
		maxActiveSeqNo = this.activeTxs.lastKey();
	    } else {
		// the plus one is because the next call to headMap() returns
		// transactions
		// that are strictly less then maxActiveSeqNo
		maxActiveSeqNo = this.committedTxs.lastKey() + 1;

	    }
	    final Iterator<TazioTransaction> cleanable = this.committedTxs
		    .headMap(maxActiveSeqNo).values().iterator();
	    while (cleanable.hasNext()) {
		final TazioTransaction tx = cleanable.next();
		final HashTableEntry entry = this.txTable.get(tx
			.getTransactionId());
		// remove details about transaction
		cleanable.remove();
		entry.setOutcome(true);
		entry.setToNullTransaction();

		// if the transaction has expired remove it from the table as
		// well
		if (entry.isExpired()) {
		    this.txTable.remove(tx.getTransactionId());
		}
	    }

	    // Now we have to find previously removed transactions that had not
	    // expired
	    final Iterator<HashTableEntry> entries = this.txTable.values()
		    .iterator();
	    while (entries.hasNext()) {
		final HashTableEntry entry = entries.next();
		if (entry.isNullTransaction()
			&& entry.isExpired()
			&& entry.getOutcome().equals(
				TransactionStatus.COMMITTED)) {

		    entries.remove();
		}
	    }
	} finally {
	    this.lock.writeLock().unlock();
	}
    }

    /**
     * Cleans the log of aborted transactions.
     * <p>
     * Aborted transactions are recorded only in the transactions hash table
     * since there's no need for detailed descriptions of aborted transactions
     * </p>
     */
    protected void cleanAbortedLog() {
	log.debug("Cleaning rolledBack transactions from the log..");
	this.lock.writeLock().lock();
	if (this.abortedTxs.isEmpty()) {
	    log
		    .debug("Nothing to clean: no rolled back transactions in the log");
	    return;
	}
	try {
	    for (int i = 0; i < this.abortedTxs.size(); i++) {
		// elements are in time order. If I cannot remove the first
		// element
		// it is sure that I will not remove any other element
		final TID abortedTid = this.abortedTxs.getFirst();
		if (this.txTable.get(abortedTid).isExpired()) {
		    this.txTable.remove(abortedTid);
		    this.abortedTxs.removeFirst();
		} else {
		    /*
		     * since aborted txs in abortedTxs are in time order I am
		     * sure that if I find a transactions which is not expired,
		     * then following transaction will not be expired too
		     */
		    break;
		}
	    }
	} finally {
	    this.lock.writeLock().unlock();
	}
    }

    /**
     * Adds a {@link Transaction} object to the <tt>TransactionsLog</tt> and
     * registers that transaction to the list of transactions whose lifecycle is
     * monitored by the log.
     * 
     * @param tx
     *            The transaction to log
     */
    @Override
    public void logTransaction(final TazioTransaction tx) {
	final long seqno = tx.getSeqNo();
	LogBucket buck = null; // It will be filled if the transaction
	// is added to the committed map, otherwise
	// it will point to the correct bucket

	tx.registerStateObserver(this);
	this.lock.writeLock().lock();
	try {
	    if (tx.isActive()) {
		// If there is already a LogBucket for the given seqNo
		buck = this.activeTxs.get(seqno);
		if (buck == null) { // create the bucket
		    buck = new LogBucket(seqno);
		    this.activeTxs.put(seqno, buck);
		}
		buck.addToBucket(tx);
		this.activeTxSize++;

		// add to the hastable too
		this.txTable.put(tx.getTransactionId(), new HashTableEntry(
			buck, tx));
	    } else if (tx.getStatus().equals(TransactionStatus.ROLLED_BACK)) {
		this.abortedTxs.add(tx.getTransactionId());
		addAbortedTransactionInTable(tx);

	    } else if (tx.getStatus().equals(TransactionStatus.COMMITTED)) {
		this.committedTxs.put(seqno, tx);
		// add to the hastable too
		this.txTable.put(tx.getTransactionId(), new HashTableEntry(
			buck, tx));
	    } else {
		log.error("Invalid Transaction State: {}", tx.getStatus());
		throw new TazioRuntimeException("Unexpected tx state");
	    }

	} finally {
	    this.lock.writeLock().unlock();
	}
    }

    /**
     * @param tx
     */
    private void addAbortedTransactionInTable(final TazioTransaction tx) {
	final HashTableEntry abortedEntry = new HashTableEntry(null,
		TazioTransactionImpl.NULL_TRANSACTION);
	abortedEntry.setOutcome(false);
	this.txTable.put(tx.getTransactionId(), abortedEntry);
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.TxStateObserver#statusChanged(org.
     * mentalsmash.tazio.transactions.TransactionStatus,
     * org.mentalsmash.tazio.transactions.TransactionStatus, java.util.UUID)
     */
    @Override
    public void statusChanged(final TransactionStatus from,
	    final TransactionStatus to, final TID tid) {
	// Transaction state changes from an inactive status are not interesting
	if (!isActiveStatus(from)) {
	    return;
	}

	// In the same way we are not interested in status changes from active
	// statuses to active status
	if (isActiveStatus(to)) {
	    return;
	}

	// No we are sure that the transaction has changed its state from an
	// active
	// state to an in inactive state
	this.lock.writeLock().lock();
	try {
	    final HashTableEntry entry = this.txTable.get(tid);

	    if (entry == null) {
		log
			.warn(
				"No transaction having TID {} in the log. Doing nothing",
				tid);
		return;
	    }

	    switch (to) {
	    case COMMITTED:
		moveToCommitted(entry);
		break;
	    case ROLLED_BACK:
		moveToRolledBack(entry);
	    }

	} finally {
	    this.lock.writeLock().unlock();
	}

    }

    /**
     * Moves the transaction pointed by the given {@link HashTableEntry} to the
     * RolledBack Map. The transaction is assumed to be in the active
     * transactions map.
     * <p>
     * This method also assumes that the write lock is retained by the calling
     * thread when this method is invoked.
     * </p>
     * 
     * @param entry
     *            The hashtable entry relative to the transaciton to move
     */
    private void moveToRolledBack(final HashTableEntry entry) {
	final TazioTransaction tx = entry.getTransaction();
	final LogBucket sourceBucket = entry.getBucket();
	final long seqno = sourceBucket.getSeqNo();
	boolean removed = false;

	// remove the transaction from the bucket
	removed = sourceBucket.removeFromBucket(tx);
	if (!removed) {
	    log.warn("Tried to remove a transaction from its bucket. But "
		    + "that transaction was not there");
	}

	if (sourceBucket.isEmpty()) { // if there are no more transactions in
	    // this bucket, remove it
	    this.activeTxs.remove(seqno);
	}

	// add the transaction to the rolled back list and set the outcome in
	// the hashtable
	this.abortedTxs.add(tx.getTransactionId());
	entry.setOutcome(false);
	// as soon as a transactions rolls back I can remove its details
	entry.setToNullTransaction();

    }

    /**
     * Moves the transaction pointed by the given {@link HashTableEntry} to the
     * Committed Map. The transaction is assumed to be in the active
     * transactions map.
     * 
     * @param entry
     *            The hashtable entry relative to the transaciton to move
     */
    private void moveToCommitted(final HashTableEntry entry) {
	final TazioTransaction tx = entry.getTransaction();
	final LogBucket sourceBucket = entry.getBucket();
	final long seqno = sourceBucket.getSeqNo();
	boolean removed = false;

	// remove the transaction from the bucket
	removed = sourceBucket.removeFromBucket(tx);
	if (!removed) {
	    log.warn("Tried to remove a transaction from its bucket. But "
		    + "that transaction was not there");
	}

	if (sourceBucket.isEmpty()) { // if there are no more transactions in
	    // this bucket, remove it
	    this.activeTxs.remove(seqno);
	}
	this.activeTxSize--;

	// add to the committed map and set the outcome in the hashtable
	this.committedTxs.put(tx.getSeqNo(), tx);
	entry.setOutcome(true);

    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mentalsmash.tazio.transactions.TransactionsLog#getActive()
     */
    // SYNCHRONIZATION: activeTx is synchrnoized, therefore there shouldn't be
    // problems
    @Override
    public List<TazioTransaction> getActive() {
	this.lock.readLock().lock();
	try {
	    return getActive(this.activeTxs.firstKey(), this.activeTxs
		    .lastKey() + 1);
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.mentalsmash.tazio.transactions.TransactionsLog#getCommitted()
     */
    @Override
    public List<TazioTransaction> getCommitted() {
	final ArrayList<TazioTransaction> committed;
	this.lock.readLock().lock();
	try {
	    committed = new ArrayList<TazioTransaction>(this.committedTxs
		    .values());
	    return committed;
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.TransactionsLog#getCommittedTx(long)
     */
    @Override
    public TazioTransaction getCommittedTx(final long seqNo) {
	if (seqNo < 0) {
	    log.error("Negative seqNo requested: {}", seqNo);
	    throw new IllegalSeqNoException("Negative seqNo");
	}
	this.lock.readLock().lock();
	try {
	    final TazioTransaction tx = this.committedTxs.get(seqNo);
	    return tx;
	} finally {
	    this.lock.readLock().unlock();
	}

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.TransactionsLog#getLastCommitted()
     */
    @Override
    public TazioTransaction getLastCommitted() {
	this.lock.readLock().lock();
	try {
	    final Entry<Long, TazioTransaction> entry = this.committedTxs
		    .lastEntry();
	    if (entry != null) {
		return this.committedTxs.lastEntry().getValue();
	    } else {
		return null;
	    }
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.log.TransactionsLog#getActive(long,
     * long)
     */
    @Override
    public List<TazioTransaction> getActive(long fromSeqNo, long toSeqNo) {

	final ArrayList<TazioTransaction> active;
	this.lock.readLock().lock();
	try {
	    if (activeTxs.isEmpty()) {
		return new ArrayList<TazioTransaction>();
	    }

	    if (toSeqNo < 0) {
		toSeqNo = activeTxs.lastKey();
	    } else if (toSeqNo < fromSeqNo) {
		throw new IllegalArgumentException(
			"toSeqNo must be greater or equal then fromSeqNo");
	    }

	    if (fromSeqNo < 0) {
		fromSeqNo = activeTxs.firstKey();
	    }
	    active = new ArrayList<TazioTransaction>(this.activeTxSize); // worst
	    // case

	    for (final LogBucket bucket : this.activeTxs.subMap(fromSeqNo,
		    toSeqNo).values()) {
		active.addAll(bucket.getTransactions());
	    }
	    return active;
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.log.TransactionsLog#getCommitted(long,
     * long)
     */
    @Override
    public List<TazioTransaction> getCommitted(long fromSeqNo, long toSeqNo) {

	final ArrayList<TazioTransaction> committed;
	this.lock.readLock().lock();
	try {
	    if (committedTxs.isEmpty() || fromSeqNo > toSeqNo) {
		return new ArrayList<TazioTransaction>();
	    }
	    if (toSeqNo < 0) {
		toSeqNo = committedTxs.lastKey();
	    } 
	    

	    if (fromSeqNo < 0) {
		fromSeqNo = committedTxs.firstKey();
	    }

	    committed = new ArrayList<TazioTransaction>(this.committedTxs
		    .subMap(fromSeqNo, toSeqNo).values());

	    return committed;
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @seeorg.mentalsmash.tazio.transactions.log.TransactionsLog#getByTID(org.
     * mentalsmash.tazio.transactions.TID)
     */
    @Override
    public TazioTransaction getByTID(final TID tid) {
	this.lock.readLock().lock();
	try {
	    final HashTableEntry entry = this.txTable.get(tid);
	    if (entry == null
		    || entry.getTransaction().equals(
			    TazioTransactionImpl.NULL_TRANSACTION)) {
		return null;
	    }
	    return entry.getTransaction();
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    private boolean isActiveStatus(final TransactionStatus status) {
	final boolean notActive = status
		.equals(TransactionStatus.NO_TRANSACTION)
		|| status.equals(TransactionStatus.COMMITTED)
		|| status.equals(TransactionStatus.ROLLED_BACK);
	return !notActive;
    }

    /**
     * Entry for the transactions hash map the provides quick retrival of
     * transactions in the log.
     * 
     * @version 0.0.1 23/ott/2009
     * @author Andrea Reale <andrea.reale@gmail.com>
     * 
     */
    final class HashTableEntry {
	private final static int SECS_IN_A_MIN = 60;
	private final static int MILLIS_IN_A_SEC = 1000;

	private final LogBucket bucket;
	private TazioTransaction transaction;
	private final Date expirationDate;

	/*
	 * Null if we the transaction is in the activeTxs or committedTxs map
	 */
	private TransactionStatus outcome = null;

	public HashTableEntry(final LogBucket bucket, final TazioTransaction tx) {

	    if (tx == null) {
		log
			.error("Someone tried to create an hashtable entry with a null"
				+ " transaction pointer");
		throw new TazioRuntimeException(
			"Reference to transactions in HashTable "
				+ "should never be null");
	    }
	    this.bucket = bucket;
	    this.transaction = tx;
	    this.expirationDate = new Date(System.currentTimeMillis()
		    + minutesToMillis(getTransactionValidity()));
	}

	/**
	 * @return the bucket, or <code>null</code> if it is a committed
	 *         transaction
	 */
	public LogBucket getBucket() {
	    return this.bucket;
	}

	/**
	 * @return the transaction
	 */
	public TazioTransaction getTransaction() {
	    return this.transaction;
	}

	/**
	 * Set the transaction associated to the entry to the NULL_TRANSACTION
	 * This is done when transactions details are removed from the log but
	 * we need to keep track of the transaction outcome in the table.
	 */
	public void setToNullTransaction() {
	    this.transaction = TazioTransactionImpl.NULL_TRANSACTION;
	}

	public boolean isNullTransaction() {
	    return this.transaction
		    .equals(TazioTransactionImpl.NULL_TRANSACTION);
	}

	/**
	 * Returns true if the transaction in the entry is expired,
	 * 
	 * @see BasicTransactionsLog#getTransactionValidity()
	 * @return true if the transaction in this entry was added to the log
	 *         more then
	 *         {@link BasicTransactionsLog#getTransactionValidity()} minutes
	 *         ago
	 */
	public boolean isExpired() {
	    return (new Date().after(this.expirationDate));
	}

	/**
	 * Returns the outcome of the transaction in this entri.
	 * <p>
	 * <strong>IMPORTANT</strong> this field is not <code>null</code> only
	 * for aborted transactions or for committed transactions that were
	 * cleaned from the log but for which the expirationDate is not yet
	 * passed
	 * </p>
	 * 
	 * @return
	 */
	public TransactionStatus getOutcome() {
	    return this.outcome;
	}

	/**
	 * Sets the outcome of a rolled back transaction or of a committed
	 * transaction that was cleaned from the log
	 * 
	 * @param committed
	 *            <code>true</code> if the outcome is
	 *            {@link TransactionStatus#COMMITTED} <code>false</code> if
	 *            the outcome is {@link TransactionStatus#ROLLED_BACK}
	 */
	public void setOutcome(final boolean committed) {
	    if (committed) {
		this.outcome = TransactionStatus.COMMITTED;
	    } else {
		this.outcome = TransactionStatus.ROLLED_BACK;
	    }
	}

	/**
	 * Converts minutes to milliseconds
	 * 
	 * @param minutes
	 *            the minutes
	 * @return the milliseconds
	 */
	private long minutesToMillis(final int minutes) {
	    return minutes * SECS_IN_A_MIN * MILLIS_IN_A_SEC;
	}

    }

    /*
     * (non-Javadoc)
     * 
     * @see
     * org.mentalsmash.tazio.transactions.log.TransactionsLog#getStatusByTID
     * (org.mentalsmash.tazio.transactions.TID)
     */
    @Override
    public TransactionStatus getStatusByTID(final TID tid) {
	this.lock.readLock().lock();
	try {
	    final HashTableEntry entry = this.txTable.get(tid);
	    if (entry == null) {
		return null;
	    }
	    if (entry.getTransaction().equals(
		    TazioTransactionImpl.NULL_TRANSACTION)) {
		return entry.getOutcome();
	    } else {
		return entry.getTransaction().getStatus();
	    }
	} finally {
	    this.lock.readLock().unlock();
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#hashCode()
     */
    @Override
    public int hashCode() {
	final int prime = 31;
	int result = 1;
	result = prime * result
		+ ((this.abortedTxs == null) ? 0 : this.abortedTxs.hashCode());
	result = prime * result + this.activeTxSize;
	result = prime * result
		+ ((this.activeTxs == null) ? 0 : this.activeTxs.hashCode());
	result = prime
		* result
		+ ((this.committedTxs == null) ? 0 : this.committedTxs
			.hashCode());
	result = prime * result
		+ ((this.lock == null) ? 0 : this.lock.hashCode());
	result = prime * result + this.unGiorno;
	result = prime * result
		+ ((this.txTable == null) ? 0 : this.txTable.hashCode());
	return result;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#equals(java.lang.Object)
     */
    @Override
    public boolean equals(final Object obj) {
	if (this == obj) {
	    return true;
	}
	if (obj == null) {
	    return false;
	}
	if (getClass() != obj.getClass()) {
	    return false;
	}
	final BasicTransactionsLog other = (BasicTransactionsLog) obj;
	if (this.abortedTxs == null) {
	    if (other.abortedTxs != null) {
		return false;
	    }
	} else if (!this.abortedTxs.equals(other.abortedTxs)) {
	    return false;
	}
	if (this.activeTxSize != other.activeTxSize) {
	    return false;
	}
	if (this.activeTxs == null) {
	    if (other.activeTxs != null) {
		return false;
	    }
	} else if (!this.activeTxs.equals(other.activeTxs)) {
	    return false;
	}
	if (this.committedTxs == null) {
	    if (other.committedTxs != null) {
		return false;
	    }
	} else if (!this.committedTxs.equals(other.committedTxs)) {
	    return false;
	}
	if (this.lock == null) {
	    if (other.lock != null) {
		return false;
	    }
	} else if (!this.lock.equals(other.lock)) {
	    return false;
	}
	if (this.unGiorno != other.unGiorno) {
	    return false;
	}
	if (this.txTable == null) {
	    if (other.txTable != null) {
		return false;
	    }
	} else if (!this.txTable.equals(other.txTable)) {
	    return false;
	}
	return true;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
	return LOG_NAME;
    }

}
